/**
 * 
 */
package com.xbang.core.dao.support;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;

import com.opensymphony.xwork2.util.ResolverUtil;
import com.xbang.core.config.InitMonth;
import com.xbang.core.config.Inject;
import com.xbang.core.dao.search.dialect.SearchDialect;
import com.xbang.core.page.Result;
import com.xbang.core.utils.SearchUtils;
import com.xbang.core.utils.TypeConverterUtils;

/**
 * Generic Dao Support including basic operations for both hibernate and jdbc
 * 
 * @author dev.zhang
 */
@Repository("genericDaoSupport")
public class GenericDaoSupport {

	private HibernateTemplate hibernateTemplate;

	private SimpleJdbcTemplate simpleJdbcTemplate;

	private SearchDialect searchDialect;

	private Map<String, Class<?>> persistMappings;

	@Inject("entity.package")
	private String persistLocation;

	/**
	 * Set the Hibernate SessionFactory to be used by this DAO. Will
	 * automatically create a HibernateTemplate for the given SessionFactory.
	 * 
	 * @see #createHibernateTemplate
	 * @see #setHibernateTemplate
	 */
	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.hibernateTemplate = createHibernateTemplate(sessionFactory);
	}

	/**
	 * Set the JDBC DataSource to be used by this DAO.
	 */
	@Autowired
	public void setDataSource(DataSource dataSource) {
		initJdbcTemplateConfig(createJdbcTemplate(dataSource));
		initTemplateConfig();
	}

	/**
	 * @param searchDialect
	 *            the searchDialect to set
	 */
	@Autowired
	public void setSearchDialect(SearchDialect searchDialect) {
		this.searchDialect = searchDialect;
	}

	/**
	 * Set persist locations
	 * 
	 * @param locations
	 */
	public void setPersistLocation(String persistLocation) {

		this.persistLocation = persistLocation;

	}

	@InitMonth(value = "init")
	public void init() {
		// /////
		initPersistMappings(persistLocation);
	}

	/**
	 * Create a HibernateTemplate for the given SessionFactory. Only invoked if
	 * populating the DAO with a SessionFactory reference!
	 * <p>
	 * Can be overridden in subclasses to provide a HibernateTemplate instance
	 * with different configuration, or a custom HibernateTemplate subclass.
	 * 
	 * @param sessionFactory
	 *            the Hibernate SessionFactory to create a HibernateTemplate for
	 * @return the new HibernateTemplate instance
	 * @see #setSessionFactory
	 */
	protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {
		return new HibernateTemplate(sessionFactory);
	}

	/**
	 * Return the HibernateTemplate for this DAO, pre-initialized with the
	 * SessionFactory or set explicitly.
	 * <p>
	 * <b>Note: The returned HibernateTemplate is a shared instance.</b> You may
	 * introspect its configuration, but not modify the configuration (other
	 * than from within an {@link #initDao} implementation). Consider creating a
	 * custom HibernateTemplate instance via
	 * <code>new HibernateTemplate(getSessionFactory())</code>, in which case
	 * you're allowed to customize the settings on the resulting instance.
	 */
	public final HibernateTemplate getHibernateTemplate() {
		return this.hibernateTemplate;
	}

	/**
	 * Create a JdbcTemplate for the given DataSource. Only invoked if
	 * populating the DAO with a DataSource reference!
	 * <p>
	 * Can be overridden in subclasses to provide a JdbcTemplate instance with
	 * different configuration, or a custom JdbcTemplate subclass.
	 * 
	 * @param dataSource
	 *            the JDBC DataSource to create a JdbcTemplate for
	 * @return the new JdbcTemplate instance
	 * @see #setDataSource
	 */
	protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}

	/**
	 * Create a SimpleJdbcTemplate based on the configured JdbcTemplate.
	 * 
	 * @param jdbcTemplate
	 */
	protected void initJdbcTemplateConfig(JdbcTemplate jdbcTemplate) {
		this.simpleJdbcTemplate = new SimpleJdbcTemplate(jdbcTemplate);
	}

	/**
	 * Used for subclass to init other jdbcTemplate configs
	 */
	protected void initTemplateConfig() {

	}

	/**
	 * Init persist mappings
	 * 
	 * @param locations
	 */
	protected void initPersistMappings(String... locations) {

		this.persistMappings = new HashMap<String, Class<?>>();

		ResolverUtil<Object> resolver = new ResolverUtil<Object>();
		resolver.find(new ResolverUtil.Test() {
			@SuppressWarnings("unchecked")
			public boolean matches(Class type) {
				return true;
			}
		}, locations);

		Set<Class<?>> classes = resolver.getClasses();

		for (Class<?> persistentClass : classes) {
			this.persistMappings.put(SearchUtils.getAlias(persistentClass), persistentClass);
		}

	}

	/**
	 * Return a SimpleJdbcTemplate wrapping the configured JdbcTemplate.
	 */
	public SimpleJdbcTemplate getJdbcTemplate() {
		return this.simpleJdbcTemplate;
	}

	/**
	 * Get limited SQL Sentence
	 * 
	 * @param dialect
	 * @param sentence
	 * @param beginIndex
	 * @param maxResult
	 * @param putValue
	 * @return
	 */
	protected String getLimitedSentence(SearchDialect dialect, String sentence, int beginIndex, int maxResult, boolean putValue) {
		return dialect.getSearchLimitString(sentence, beginIndex, maxResult, putValue);
	}

	/**
	 * Check to see if this class has hibernate support
	 * 
	 * @return
	 */
	public boolean hasHibernateSupport() {
		return this.hibernateTemplate != null;
	}

	// =============================================================================================
	//
	// Generic Hibernate Operations
	//
	// =============================================================================================

	/**
	 * Save entity using hibernateTemplate
	 * 
	 * @param entity
	 * @return
	 */
	public Serializable save(Object entity) {
		return getHibernateTemplate().save(entity);
	}

	/**
	 * Update entity using hibernateTemplate
	 * 
	 * @param entity
	 */
	public void update(Object entity) {
		getHibernateTemplate().update(entity);
	}

	/**
	 * Save or update entity using hibernateTemplate
	 * 
	 * @param entity
	 */
	public void saveOrUpdate(Object entity) {
		getHibernateTemplate().saveOrUpdate(entity);
	}

	/**
	 * Delete entity using hibernateTemplate
	 * 
	 * @param entity
	 */
	public void delete(Object entity) {
		getHibernateTemplate().delete(entity);
	}

	/**
	 * Delete entity according to persistentClass and primary key using
	 * hibernateTemplate
	 * 
	 * @param <T>
	 * @param persistentClass
	 * @param id
	 */
	public <T> void delete(Class<T> persistentClass, Serializable id) {
		delete(load(persistentClass, id));
	}

	/**
	 * Load entity according to persistentClass and primary key using
	 * hibernateTemplate
	 * 
	 * @param <T>
	 * @param persistentClass
	 * @param id
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T load(Class<T> persistentClass, Serializable id) {
		return (T) getHibernateTemplate().load(persistentClass, id);
	}

	/**
	 * Load all the entities according to persistentClass using
	 * hibernateTemplate
	 * 
	 * @param <T>
	 * @param persistentClass
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> List<T> loadAll(Class<T> persistentClass) {
		return getHibernateTemplate().loadAll(persistentClass);
	}

	/**
	 * Get entity according to persistentClass and primary key using
	 * hibernateTemplate
	 * 
	 * @param <T>
	 * @param persistentClass
	 * @param id
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(Class<T> persistentClass, Serializable id) {
		return (T) getHibernateTemplate().get(persistentClass, id);
	}

	/**
	 * Get entity count according to persistentClass using hibernate callback
	 * 
	 * @param persistentClass
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public int count(final Class persistentClass) {
		return ((Long) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				String querySentence = SearchUtils.getHQLCountSentence(persistentClass);
				Query query = session.createQuery(querySentence);
				return (Long) query.iterate().next();
			}
		})).intValue();
	}

	/**
	 * Get entity list from beginIndex to the number of maxResult
	 * 
	 * @param <T>
	 * @param persistentClass
	 * @param beginIndex
	 * @param maxResult
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> List<T> list(final Class<T> persistentClass, final int beginIndex, final int maxResult) {
		return (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				String querySentence = SearchUtils.getQuerySentence(persistentClass);
				Query query = session.createQuery(querySentence);
				query.setFirstResult(beginIndex);
				query.setMaxResults(maxResult);
				return query.list();
			}
		});
	}

	/**
	 * Search by hibernate for int result, using Map basic parameters. Mostly
	 * used for HQL like: 'SELECT count(*) ...'
	 * 
	 * @param sentence
	 * @param parameters
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public int searchForInt(final String sentence, final Map<String, Object> parameters) {
		List result = (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				Query query = session.createQuery(sentence);
				query.setProperties(parameters);
				return query.list();
			}
		});

		// deal with the condition when hql contains group by
		return result != null && !result.isEmpty() ? (result.size() > 1 ? result.size() : ((Long) result.get(0)).intValue()) : 0;
	}

	/**
	 * Search by hibernate for int result, using a typical JavaBean as basic
	 * parameters. Mostly used for HQL like: 'SELECT count(*) ...'
	 * 
	 * @param sentence
	 * @param properties
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public int searchForInt(final String sentence, final Object properties) {
		List result = (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				Query query = session.createQuery(sentence);
				query.setProperties(properties);
				return query.list();
			}
		});

		// deal with the condition when hql contains group by
		return result.size() > 1 ? result.size() : ((Long) result.get(0)).intValue();
	}

	/**
	 * Search by hibernate for list result, using Map basic parameter.
	 * 
	 * @param sentence
	 * @param parameters
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List searchForList(final String sentence, final Map<String, Object> parameters) {
		return (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				Query query = session.createQuery(sentence);
				query.setProperties(parameters);
				return query.list();
			}
		});
	}
	
//	protected Result getPageList(String hql,String alias,int firstRow,int rowPerPage){
//		Query query =getSession().createQuery(hql);
//		query.setFirstResult(firstRow);
//		query.setMaxResults(rowPerPage);
//		List<?> list = query.list();
//		Long row = null;
//        Iterator<Long> iter =getSession().createQuery("select count("+alias+") " + hql).iterate();
//        while (iter.hasNext()) {
//            row = iter.next();
//        }
//        return new Result(list, new Integer(row.intValue()));
//	}
//	
//	
//	protected Session getSession(){
//		getHibernateTemplate().getSessionFactory().openSession();
//	}
	/**
	 * Search by hibernate for list result, using a typical JavaBean as basic
	 * parameters.
	 * 
	 * @param sentence
	 * @param properties
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List searchForList(final String sentence, final Object properties) {
		if (TypeConverterUtils.isAcceptablePrimitiveType(properties.getClass())) {
			return searchForList(sentence, new Object[] { properties });
		} else {
			return (List) getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException {
					Query query = session.createQuery(sentence);
					query.setProperties(properties);
					return query.list();
				}
			});
		}
	}

	/**
	 * Search by hibernate for list result using parameters array
	 * 
	 * @param sentence
	 * @param parameters
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List searchForList(final String sentence, Object... parameters) {
		Map<String, Object> namedParameters = SearchUtils.getNamedParameters(sentence, parameters);
		return searchForList(sentence, namedParameters);
	}

	/**
	 * Search by hibernate for list result from beginIndex to the number of
	 * maxResult, using Map as basic parameter.
	 * 
	 * @param sentence
	 * @param parameters
	 * @param beginIndex
	 * @param maxResult
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List searchForList(final String sentence, final Map<String, Object> parameters, final int beginIndex, final int maxResult) {
		return (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				Query query = session.createQuery(sentence);
				query.setFirstResult(beginIndex);
				query.setMaxResults(maxResult);
				query.setProperties(parameters);
				return query.list();
			}
		});
	}

	/**
	 * Search by hibernate for list result from beginIndex to the number of
	 * maxResult, using a typical JavaBean as basic parameters.
	 * 
	 * @param sentence
	 * @param properties
	 * @param beginIndex
	 * @param maxResult
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List searchForList(final String sentence, final Object properties, final int beginIndex, final int maxResult) {
		return (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException {
				Query query = session.createQuery(sentence);
				query.setFirstResult(beginIndex);
				query.setMaxResults(maxResult);
				query.setProperties(properties);
				return query.list();
			}
		});
	}

	// =============================================================================================
	//
	// Generic JDBC Operations
	//
	// =============================================================================================

	/**
	 * Query by JDBC for int result, using Map as basic parameter.
	 * 
	 * @param sentence
	 * @param parameters
	 * @return
	 */
	public int queryForInt(String sentence, Map<String, Object> parameters) {
		return getJdbcTemplate().queryForInt(sentence, parameters);
	}

	/**
	 * Query by JDBC for int result, using a typical JavaBean as basic
	 * parameters.
	 * 
	 * @param sentence
	 * @param properties
	 * @return
	 */
	public int queryForInt(String sentence, Object properties) {
		SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(properties);
		return getJdbcTemplate().queryForInt(sentence, namedParameters);
	}

	/**
	 * Query by JDBC for List result, using Map as basic parameter.
	 * 
	 * @param sentence
	 * @param parameters
	 * @return
	 */
	public List<Map<String, Object>> queryForList(String sentence, Map<String, Object> parameters) {
		return getJdbcTemplate().queryForList(sentence, parameters);
	}

	/**
	 * Query by JDBC for List result, using a typical JavaBean as basic
	 * parameters.
	 * 
	 * @param sentence
	 * @param properties
	 * @return
	 */
	public List<Map<String, Object>> queryForList(String sentence, Object properties) {
		SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(properties);
		return getJdbcTemplate().queryForList(sentence, namedParameters);
	}

	/**
	 * Query by JDBC for list result from beginIndex to the number of maxResult,
	 * using Map as basic parameter.
	 * 
	 * @param sentence
	 * @param parameters
	 * @param beginIndex
	 * @param maxResult
	 * @return
	 */
	public List<Map<String, Object>> queryForList(String sentence, Map<String, Object> parameters, int beginIndex, int maxResult) {
		parameters.put("beginIndex", beginIndex);
		parameters.put("maxResult", maxResult);
		return getJdbcTemplate().queryForList(getLimitedSentence(searchDialect, sentence, beginIndex, maxResult, false), parameters);
	}

	/**
	 * Query by JDBC for list of resultClass as result, using Map as basic
	 * parameter,
	 * 
	 * @param <T>
	 * @param sentence
	 * @param parameters
	 * @param resultClass
	 * @return
	 */
	public <T> List<T> queryForList(String sentence, Map<String, Object> parameters, Class<T> resultClass) {
		return getJdbcTemplate().query(sentence, ParameterizedBeanPropertyRowMapper.newInstance(resultClass), parameters);
	}

	/**
	 * Query by JDBC for list result, using a typical JavaBean as parameter.
	 * 
	 * @param <T>
	 * @param sentence
	 * @param properties
	 * @param resultClass
	 * @return
	 */
	public <T> List<T> queryForList(String sentence, Object properties, Class<T> resultClass) {
		SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(properties);
		return getJdbcTemplate().query(sentence, ParameterizedBeanPropertyRowMapper.newInstance(resultClass), namedParameters);
	}

	/**
	 * Query by JDBC for list of resultClass result from beginIndex to the
	 * number of maxResult, using Map as basic parameter.
	 * 
	 * @param <T>
	 * @param sentence
	 * @param parameters
	 * @param beginIndex
	 * @param maxResult
	 * @param resultClass
	 * @return
	 */
	public <T> List<T> queryForList(String sentence, Map<String, Object> parameters, int beginIndex, int maxResult, Class<T> resultClass) {
		parameters.put("beginIndex", beginIndex);
		parameters.put("maxResult", maxResult);
		return getJdbcTemplate().query(getLimitedSentence(searchDialect, sentence, beginIndex, maxResult, false),
				ParameterizedBeanPropertyRowMapper.newInstance(resultClass), parameters);
	}

	/**
	 * @return
	 */
	public Map<String, Class<?>> getPersistMappings() {
		return persistMappings;
	}

	public Class<?> getPersistMappingsByKey(String key) {

		if (persistMappings == null || persistMappings.isEmpty()) {
			return null;
		} else {
			for (Map.Entry<String, Class<?>> entry : persistMappings.entrySet()) {
				if (entry.getKey().toLowerCase().equals(key.toLowerCase())) {
					return entry.getValue();
				}
			}
		}

		return null;
	}
}
